package dslProgramGen;

import methodCallTreeGen.MethodNode;

import java.util.*;

import static dslProgramGen.util.ClearRedundantMethodsUtil.*;
import static utils.Utils.writeLineToDstFile;

/**
 * 用于函数精简，解决相同内容函数多次定义带来的冗余问题，采用的是对每个函数生成一个hash值，hash值由函数本身内容和该函数调用的函数hash值共同组成
 */
public class ClearRedundantMethodsByMethodHash {
    /**
     * 层序遍历函数调用树，将结果保存到methodNodeListsForEachLayer
     * @param rootNode 函数调用树的根节点
     * @param methodNodeListsForEachLayer 层序遍历的结果，每个元素表示一层的methodNodes
     */
    public static void traverseMethodCallTreeByLayer(MethodNode rootNode, List<List<MethodNode>> methodNodeListsForEachLayer) {
        Queue<MethodNode> queue = new LinkedList<>();
        queue.add(rootNode);
        while (!queue.isEmpty()) {
            int sizeForThisLayer = queue.size();
            List<MethodNode> methodNodeListForThisLayer = new ArrayList<>();
            for (int i = 0; i < sizeForThisLayer; i++) {
                // 对这层的每个元素遍历
                MethodNode pollMethodNode = queue.poll();
                methodNodeListForThisLayer.add(pollMethodNode);
                // 若该methodNode有调用其他函数
                if (hasSubMethodNode(pollMethodNode)) {
                    // 获得该函数调用了多少个其他函数
                    int subMethodNum = getSubMethodNum(pollMethodNode);
                    for (int j = 0; j < subMethodNum; j++) {
                        MethodNode nthSubMethodNode = getNthSubMethodNode(pollMethodNode, j);
                        queue.add(nthSubMethodNode);
                    }
                }
            }
            methodNodeListsForEachLayer.add(methodNodeListForThisLayer);
        }
    }

    /**
     * 从层序遍历的结果由下往上对每个methodNode生成一个hash值，hash值由函数本身内容和该函数调用的函数的hash值共同构成
     * 将相同hash值的函数保存在一起 即hashCode2MethodNodeListMap value中
     * @param methodNodeListsForEachLayer 层序遍历的结果，每个元素表示一层的methodNodes
     * @param hashCode2MethodNodeListMap 保存相同hash值的methodNode
     */
    public static void calculateHashCodeForEveryMethod(List<List<MethodNode>> methodNodeListsForEachLayer, Map<Integer, List<MethodNode>> hashCode2MethodNodeListMap) {
        for (int i = methodNodeListsForEachLayer.size() - 1; i >= 0; i--) {
            List<MethodNode> methodNodeListForThisLayer = methodNodeListsForEachLayer.get(i);
            // 对methodNodeListForThisLayer里面的每个元素进行遍历生成hash值
            for (MethodNode methodNodeTemp : methodNodeListForThisLayer) {
                // 首先判断该methodNodeTemp有没有调用子函数
                if (hasSubMethodNode(methodNodeTemp)) {
                    // 该methodNodeTemp有调用子函数
                    // 获得该函数本身的内容，包括 参数，返回值，函数名，函数体的内容拼接到一起
                    String methodNodeContentString = getMethodNodeContentAsString(methodNodeTemp);
                    // 获得该函数调用函数的hash值的组合
                    // 获得第一个调用函数的hash值
                    int tempMethodHashCode = getNthSubMethodNode(methodNodeTemp, 0).methodHashCode;
                    // 如果调用了不止一个函数，获得调用函数的hash值组合
                    for (int j = 1; j < getSubMethodNum(methodNodeTemp); j++) {
                        tempMethodHashCode = combineTwoHashCode(tempMethodHashCode, getNthSubMethodNode(methodNodeTemp, j).methodHashCode);
                    }
                    // 将调用函数的hash值tempMethodHashCode与函数本身的内容再hash产生该函数的hash值
                    int methodHashCode = Objects.hash(methodNodeContentString, tempMethodHashCode);
                    methodNodeTemp.methodHashCode = methodHashCode;
                    // 将该methodNodeTemp加入到hashCode2MethodNodeListMap中
                    if (hashCode2MethodNodeListMap.get(methodHashCode) == null) {
                        // map中没有该hash值
                        List<MethodNode> sameHashCodeMethodList = new ArrayList<>();
                        sameHashCodeMethodList.add(methodNodeTemp);
                        hashCode2MethodNodeListMap.put(methodHashCode, sameHashCodeMethodList);
                    } else {
                        // map中有该hash值
                        List<MethodNode> sameHashCodeMethodList = hashCode2MethodNodeListMap.get(methodHashCode);
                        sameHashCodeMethodList.add(methodNodeTemp);
                    }
                } else {
                    // 该methodNodeTemp没有调用子函数
                    // 获得该函数的内容，包括 参数，返回值，函数名，函数体的内容拼接到一起
                    String methodNodeContentString = getMethodNodeContentAsString(methodNodeTemp);
                    // 产生methodNodeTemp的hash值
                    int methodHashCode = Objects.hash(methodNodeContentString);
                    methodNodeTemp.methodHashCode = methodHashCode;
                    // 将该methodNodeTemp加入到hashCode2MethodNodeListMap中
                    if (hashCode2MethodNodeListMap.get(methodHashCode) == null) {
                        // map中没有该hash值
                        List<MethodNode> sameHashCodeMethodList = new ArrayList<>();
                        sameHashCodeMethodList.add(methodNodeTemp);
                        hashCode2MethodNodeListMap.put(methodHashCode, sameHashCodeMethodList);
                    } else {
                        // map中有该hash值
                        List<MethodNode> sameHashCodeMethodList = hashCode2MethodNodeListMap.get(methodHashCode);
                        sameHashCodeMethodList.add(methodNodeTemp);
                    }
                }
            }
        }
    }

    /**
     * 函数精简，之前可能出现完全相同的两个函数被定义了两次（甚至更多次数），现在想 内容完全相同的函数只定义一次
     *
     * @param rootNode 函数调用树的根节点
     */
    public static void clearRedundantMethodDefine(MethodNode rootNode) {
        // 函数精简，对相同的函数只定义一次（相同包括函数内容及其调用的函数的内容都相同）
        // 将函数调用树中methodNode按层序遍历，每一层的结果用List<MethodNode>表示，各层的List组成一个List<List>
        List<List<MethodNode>> methodNodeListsForEachLayer = new ArrayList<>();
        traverseMethodCallTreeByLayer(rootNode, methodNodeListsForEachLayer);

        // 从层序遍历的结果由下往上对每个methodNode生成一个hash值，hash值由函数本身内容和该函数调用的函数的hash值共同构成
        // 构建一个map key:hashcode,value:List<MethodNode>，将相同hash值的methodNode保存到一起
        Map<Integer, List<MethodNode>> hashCode2MethodNodeListMap = new HashMap<>();
        calculateHashCodeForEveryMethod(methodNodeListsForEachLayer, hashCode2MethodNodeListMap);

        // hashCode2MethodNodeListMap的每一个value List<MethodNode>表示相同内容的函数集合，我们选择集合中的第一个元素作为相同函数的唯一定义
        representSameContentsMethodsUsingTheFirstMethod(hashCode2MethodNodeListMap);
    }

    /**
     * 将内容相同的函数用第一个函数表示
     *
     * @param hashCode2MethodNodeListMap key:hashcode,value:List<MethodNode>，将相同hash值的methodNode保存到一起
     */
    public static void representSameContentsMethodsUsingTheFirstMethod(Map<Integer, List<MethodNode>> hashCode2MethodNodeListMap) {
        String sameMethodNamesFilePath = "src/main/resources/output/debug/sameMethodName/sameMethodNames.txt";
        for (List<MethodNode> sameContentMethodList : hashCode2MethodNodeListMap.values()) {
            // 将函数内容相同的函数名放在一起，输出到文件，方便debug
            // 获得sameContentMethodList第一个元素作为其他相同内容函数的定义
            MethodNode firstMethodNodeOfSameContentMethodList = sameContentMethodList.get(0);
            if (DslProgramGen.isDebug) {
                writeLineToDstFile(firstMethodNodeOfSameContentMethodList.newMethodName, sameMethodNamesFilePath);
            }
            for (int i = 1; i < sameContentMethodList.size(); i++) {
                // 获得sameContentMethodList第i个methodNode
                MethodNode ithMethodNodeOfSameContentMethodList = sameContentMethodList.get(i);
                if (DslProgramGen.isDebug) {
                    writeLineToDstFile(ithMethodNodeOfSameContentMethodList.newMethodName, sameMethodNamesFilePath);
                }
                // 看看ithMethodNodeOfSameContentMethodList有没有parent节点
                if (ithMethodNodeOfSameContentMethodList.parentNode != null) {
                    // 获得ithMethodNodeOfSameContentMethodList的parentNode
                    MethodNode parentNodeOfIthMethodNode = (MethodNode) (ithMethodNodeOfSameContentMethodList.parentNode);
                    // 获得ithMethodNodeOfSameContentMethodList在parentNodeOfIthMethodNode的index
                    int ithMethodNodeIndexOfParentNodeContentList = parentNodeOfIthMethodNode.contentNodeList.indexOf(ithMethodNodeOfSameContentMethodList);
                    // 替换ithMethodNodeOfSameContentMethodList为firstMethodNodeOfSameContentMethodList
                    parentNodeOfIthMethodNode.contentNodeList.set(ithMethodNodeIndexOfParentNodeContentList, firstMethodNodeOfSameContentMethodList);
                    // parentNodeOfIthMethodNode中函数体会涉及对ithMethodNode调用语句，需要修改为firstMethodNodeOfSameContentMethodList的函数名
                    renameCallMethodNameInMethodContentList(parentNodeOfIthMethodNode.dslExpressedMethod, ithMethodNodeOfSameContentMethodList.newMethodName, firstMethodNodeOfSameContentMethodList.newMethodName);
                }
            }
            if (DslProgramGen.isDebug) {
                writeLineToDstFile("\n\n\n", sameMethodNamesFilePath);
            }
        }
    }

    /**
     * 对哈希值进行组合操作
     * @param methodHashCode1 第一个hash值
     * @param methodHashCode2 第二个hash值
     * @return 对hash值进行组合操作后的结果
     */
    public static int combineTwoHashCode(int methodHashCode1, int methodHashCode2) {
//        // 使用位异或操作
//        int xorHash = methodHashCode1 ^ methodHashCode2;
//        return xorHash;
        return 31 * methodHashCode1 + methodHashCode2;
    }

    /**
     * 将methodNode的methodName与methodNode.dslExpressedMethod的返回值，参数，函数体拼接起来为一个字符串
     * 注意 methodNode.dslExpressedMethod的函数体中的调用函数的DSL语句要去掉后缀_n
     * @param methodNode 待处理的methodNode
     * @return 拼接后的字符串
     */
    public static String getMethodNodeContentAsString(MethodNode methodNode) {
        StringBuilder stringBuilder = new StringBuilder();
        // 将函数名拼接上
        stringBuilder.append(methodNode.methodName);
        // 将函数参数拼接上
        DslExpressedMethod dslExpressedMethod = methodNode.dslExpressedMethod;
        for (String parameterStr : dslExpressedMethod.parameters) {
            stringBuilder.append(parameterStr);
        }
        // 将函数返回值拼接上
        stringBuilder.append(dslExpressedMethod.returnType);
        // 将函数体的内容拼接上
        List<String> methodContentList = dslExpressedMethod.methodContentList;
        for (int i = 0; i < methodContentList.size(); i++) {
            // 表示methodContentList第i个元素内容
            String ithDslStatementOfMethodContentList = methodContentList.get(i);
            if (isMethodCallDslStatement(ithDslStatementOfMethodContentList)) {
                // 若methodContentList第i个元素内容为函数调用
                // 重写调用语句去除_vi( 中的i
                // 例如methodName_v0(school_0, student_0) 去除后为methodName_v(school_0, student_0)
                ithDslStatementOfMethodContentList = ithDslStatementOfMethodContentList.replaceAll("_v\\d+\\(", "_v(");
                stringBuilder.append(ithDslStatementOfMethodContentList);
            } else {
                stringBuilder.append(ithDslStatementOfMethodContentList);
            }
        }
        return stringBuilder.toString();
    }


}
